//	Aviv Charikar	Yael Tzirulnikov	305343600	307955609

package main;

import java.io.*;
import java.text.DecimalFormat;

import modelTraining.*;

public class Ex2 {
	public static void main(String[] args) throws Exception {
		if (args.length < 4)
			throw new Exception("Not enough arguments");
		checkModels(args[0], args[1], args[2], args[3]);
	}
	
	private static void checkModels(String devFilename, String testFilename, String inputWord, String outputFilename) throws IOException {
		PrintWriter writer = new PrintWriter(outputFilename);
		
		writer.println("#Students\tAviv Charikar\tYael Tzirulnikov\t305343600\t307955609");
		
		writer.println("#Output1\t" + devFilename);
		writer.println("#Output2\t" + testFilename);
		writer.println("#Output3\t" + inputWord);
		writer.println("#Output4\t" + outputFilename);
		
		// The vocabulary size is known
		int vocSize = 300000;
		writer.println("#Output5\t" + vocSize);
		// In a uniform distribution each word has the same probability
		double uniProb = 1.0 / vocSize;
		writer.println("#Output6\t" + uniProb);
		
		// Reads the whole dev file with a basic model
		BasicModelTraining basic = new BasicModelTraining(devFilename);
		int eventsNo = basic.getCountAll();
		writer.println("#Output7\t" + eventsNo);
		
		// Lidstone using 90% of the event for training
		int trainSize = (int)Math.round(0.9 * eventsNo);
		LidstoneModelTraining lidstone = new LidstoneModelTraining(vocSize, devFilename, trainSize);
		writer.println("#Output8\t" + lidstone.getCountValidation());
		writer.println("#Output9\t" + lidstone.getCountTraining());
		writer.println("#Output10\t" + lidstone.getAllTrainingWords().size());
		writer.println("#Output11\t" + lidstone.getWordCount(inputWord));
		
		// Lidstone with lambda = 0 (default) i.e. MLE without smoothing
		writer.println("#Output12\t" + lidstone.getWordProbability(inputWord));
		writer.println("#Output13\t" + lidstone.getUnseenWordProbability());
		
		// Lidstone with lambda = 0.1
		lidstone.setLambda(0.1);
		writer.println("#Output14\t" + lidstone.getWordProbability(inputWord));
		writer.println("#Output15\t" + lidstone.getUnseenWordProbability());
		
		// Perplexity calculations for Lidstone with lambda = 0.01, 0.1, 1
		lidstone.setLambda(0.01);
		writer.println("#Output16\t" + lidstone.calcPerplexityOnValidationSet());
		lidstone.setLambda(0.1);
		writer.println("#Output17\t" + lidstone.calcPerplexityOnValidationSet());
		lidstone.setLambda(1);
		writer.println("#Output18\t" + lidstone.calcPerplexityOnValidationSet());
		
		// Iterates over all possible lambdas (0.0 - 2.0) and finds the one with the best perplexity
		lidstone.setLambda(0);
		double bestLambda = 0;
		double bestPerp = lidstone.calcPerplexityOnValidationSet();
		for (double lambda = 0.01; lambda <= 2; lambda += 0.01) {
			lidstone.setLambda(lambda);
			double perp = lidstone.calcPerplexityOnValidationSet();
			if (perp < bestPerp) {
				bestLambda = lambda;
				bestPerp = perp;
			}
		}
		
		lidstone.setLambda(bestLambda);
		DecimalFormat df = new DecimalFormat("#.##");
		writer.println("#Output19\t" + df.format(bestLambda));
		writer.println("#Output20\t" + bestPerp);
		
		// Held-Out model training with half training and half held-out
		trainSize = Math.round(eventsNo / 2);
		HeldOutModelTraining heldout = new HeldOutModelTraining(vocSize, devFilename, trainSize);
		writer.println("#Output21\t" + heldout.getCountTraining());
		writer.println("#Output22\t" + heldout.getCountHeldout());
		writer.println("#Output23\t" + heldout.getWordProbability(inputWord));
		writer.println("#Output24\t" + heldout.getUnseenWordProbability());
		
		// Reads the whole test file with a basic model
		BasicModelTraining testBasic = new BasicModelTraining(testFilename);
		writer.println("#Output25\t" + testBasic.getCountAll());
		
		// Calculate lidstone model perplexity on test set
		double testLidstonePerp = lidstone.calcPerplexityOnTestSet(testBasic);
		writer.println("#Output26\t" + testLidstonePerp);
		
		// Calculate held out model perplexity on test set
		double testHeldoutPerp = heldout.calcPerplexity(testBasic);
		writer.println("#Output27\t" + testHeldoutPerp);
		
		// Output the best model on the test set
		char best = (testLidstonePerp < testHeldoutPerp) ? 'L' : 'H';
		writer.println("#Output28\t" + best);
		
		// Output the table
		writer.println("#Output29");
		df = new DecimalFormat("#.#####");
		for(int i = 0; i <= 9; i++) {
			writer.print(i + "\t");
			double fLambda = lidstone.getWordFrequencyByCount(i);
			double fH = heldout.getWordFrequencyByCount(i);
			int Ni = heldout.getNumOfWordsWithCount(i);
			int ti = heldout.getTr(i);
			writer.println(df.format(fLambda) + "\t" + df.format(fH) + "\t" + Ni + "\t" + ti);
		}
		
		writer.close();
	}
}
